#include "compipewindow.h"
#include "ui_compipewindow.h"
#include <QFile>
#include <QSettings>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QDockWidget>
#include "apputils.h"

#include "luaengine.h"

#include "io_console_dialog.h"
#include "packet_dialog.h"
#include "lua_dissector.h"
#include "wmem_strutils.h"

#include "funnel.h"

#include "enabled_protocols_dialog.h"

#include "proto_tree.h"
#include "byte_view_tab.h"

static void register_console_menu_cb(const char *name,
                                         funnel_console_eval_cb_t eval_cb,
                                         funnel_console_open_cb_t open_cb,
                                         funnel_console_close_cb_t close_cb,
                                         void *callback_data)
{
    IOConsoleDialog *console = new IOConsoleDialog(NULL,
                                                   name,
                                                   eval_cb,
                                                   open_cb,
                                                   close_cb,
                                                   callback_data);

    console->setAttribute(Qt::WA_DeleteOnClose);
    console->show();
    console->raise();
    console->activateWindow();
}

ComPipeWindow::ComPipeWindow(QWidget *parent):
    QMainWindow(parent),
    ui(new Ui::ComPipeWindow),
    m_serialportL(nullptr),
    m_serialportR(nullptr),
    m_l2r_cnt(0),
    m_r2l_cnt(0),
    ldt_(nullptr)
{
    ui->setupUi(this);

    m_prototreeDock = new QDockWidget("Protocol Tree", this);
    m_prototreeDock->setAllowedAreas(Qt::BottomDockWidgetArea);
    addDockWidget(Qt::BottomDockWidgetArea, m_prototreeDock);
    m_prototree = new ProtoTree(this);
    m_prototreeDock->setWidget(m_prototree);

    m_byteviewDock = new QDockWidget("Bytes View", this);
    m_byteviewDock->setAllowedAreas(Qt::BottomDockWidgetArea);
    addDockWidget(Qt::BottomDockWidgetArea, m_byteviewDock);
    m_byteview = new ByteViewTab(this);
    m_byteviewDock->setWidget(m_byteview);

    this->tabifyDockWidget(m_prototreeDock, m_byteviewDock);
    m_prototreeDock->raise();

    QFont mono_font_ = AppUtils::monoFont();

    m_prototree->setMonospaceFont(mono_font_);
    m_byteview->setMonospaceFont(mono_font_);

    QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts();

    foreach (QSerialPortInfo port, ports) {
        ui->portL->addItem(port.portName());
        ui->portR->addItem(port.portName());
    }
    ui->portL->setCurrentIndex(0);
    ui->portR->setCurrentIndex(0);

    QList<qint32> baudRates = QSerialPortInfo::standardBaudRates();
    baudRates.removeOne(110);
    baudRates.removeOne(300);
    baudRates.removeOne(600);
    baudRates.removeOne(1200);
    baudRates.removeOne(14400);
    baudRates.removeOne(56000);
    foreach (qint32 baud, baudRates) {
        ui->baudRateL->addItem(QString::number(baud));
        ui->baudRateR->addItem(QString::number(baud));
    }
    ui->baudRateL->setCurrentIndex(0);
    ui->baudRateR->setCurrentIndex(0);

    connect(ui->action_Scan, &QAction::triggered, this, &ComPipeWindow::scanActionTriggered);
    connect(ui->action_Start, &QAction::triggered, this, &ComPipeWindow::startActionTriggered);
    connect(ui->action_Stop, &QAction::triggered, this, &ComPipeWindow::stopActionTriggered);
    connect(ui->action_Exit, &QAction::triggered, this, &ComPipeWindow::exitActionTriggered);

    connect(ui->action_Scroll, &QAction::triggered, this, [this]{
        if (ui->action_Scroll->isChecked()) {
            ui->action_Scroll->setChecked(true);
            ui->info->setAutoScrollToBottom(true);
        } else {
            ui->action_Scroll->setChecked(false);
            ui->info->setAutoScrollToBottom(false);
        }
    });

    connect(ui->action_EnabledProtocols, &QAction::triggered, this, [this]{
        EnabledProtocolsDialog *enable_proto_dialog = new EnabledProtocolsDialog(this);
//        connect(enable_proto_dialog, &EnabledProtocolsDialog::destroyed, mainApp, &MainApplication::flushAppSignals);

        enable_proto_dialog->setWindowModality(Qt::ApplicationModal);
        enable_proto_dialog->setAttribute(Qt::WA_DeleteOnClose);
        enable_proto_dialog->show();
    });
    connect(ui->action_ReloadLua, &QAction::triggered, this, &ComPipeWindow::reloadLuaPlugins);
    connect(ui->action_LuaConsol, &QAction::triggered, this, [this]{
        funnel_register_all_console_menus(register_console_menu_cb);
    });

    openEnable(false);

    loadConfig();

    m_sfTimL2R.setTimerType(Qt::PreciseTimer);
    m_sfTimL2R.setSingleShot(true);
    m_sfTimL2R.setInterval(10);
    m_sfTimR2L.setTimerType(Qt::PreciseTimer);
    m_sfTimR2L.setSingleShot(true);
    m_sfTimR2L.setInterval(10);
    m_sfCntL2R = 0;
    m_sfCntR2L = 0;
    connect(&m_sfTimL2R, &QTimer::timeout, this, [this]{
        QString info, detail;

        info = QString("L>R <%1>  %2").arg(m_sfCntL2R).arg(QString::fromUtf8(m_sfBufL2R));
        detail = QString('[') + m_sfBufL2R.toHex(' ').toUpper() + QString(']');

        ui->info->showInfo(info, m_sfBufL2R);
        m_sfBufL2R.clear();m_sfCntL2R = 0;
    });
    connect(&m_sfTimR2L, &QTimer::timeout, this, [this]{
        QString info, detail;

        info = QString("R>L <%1>  %2").arg(m_sfCntR2L).arg(QString::fromUtf8(m_sfBufR2L));
        detail = QString('[') + m_sfBufR2L.toHex(' ').toUpper() + QString(']');

        ui->info->showInfo(info, m_sfBufR2L);
        m_sfBufR2L.clear();m_sfCntR2L = 0;
    });

    connect(ui->info, &LInfoListWidget::itemClicked, this, [this](QListWidgetItem *item){
        QByteArray fdata = ui->info->frame(ui->info->row(item));

        tvbuff_t *tvb = NULL;
        uint8_t *data;
        int len = fdata.length();
        if (len <= 0) return;
        data = (uint8_t*)wmem_dup((const void*)fdata.data(), len);
        tvb = tvb_new_real_data(data, len, len);
        tvb_set_free_cb(tvb, free);

        lua_dissect_free(ldt_);

        /* proto tree, visible. We need a proto tree if there are custom columns */
        ldt_ = lua_dissect_new();
    //    col_custom_prime_edt(&edt_, &(cap_file_.capFile()->cinfo));

        lua_dissect_run(ldt_, tvb);
    //    epan_dissect_fill_in_columns(&edt_, true, true);

        m_prototree->setDissector(ldt_);
        m_byteview->setDissector(ldt_);
        m_prototree->setRootNode(ldt_->tree);
        m_byteview->selectedFrameChanged(QList<int>() << 0);
    });
    connect(ui->info, &LInfoListWidget::itemDoubleClicked, this, [this](QListWidgetItem *item){
        PacketDialog *pktDialog = new PacketDialog(this, ui->info->frame(ui->info->row(item)));
        pktDialog->show();
    });

    connect(m_byteview, SIGNAL(fieldSelected(FieldInformation *)),
            m_prototree, SLOT(selectedFieldChanged(FieldInformation *)));
    connect(m_prototree, SIGNAL(fieldSelected(FieldInformation *)),
            m_byteview, SLOT(selectedFieldChanged(FieldInformation *)));
}

ComPipeWindow::~ComPipeWindow()
{
    saveConfig();
    stopActionTriggered();
    delete ui;
    lua_dissect_free(ldt_);
}

void ComPipeWindow::loadConfig()
{
    const QString filename(QApplication::applicationName() + QString(".ini"));

    loadDefaultConfig();
    if(QFile(filename).exists()){
        if(readConfig()){
            return;
        }
    }
//    saveConfig();
}

void ComPipeWindow::loadDefaultConfig()
{

}

bool ComPipeWindow::readConfig()
{
    const QString filename(QApplication::applicationName() + QString(".ini"));
    QStringList grouplist;
    QString comL = "ComL";
    QString comR = "ComR";

    QSettings *settings;
    settings = new QSettings(filename, QSettings::IniFormat);
    grouplist = settings->childGroups();
    if(grouplist.contains(comL)){
        settings->beginGroup(comL);
        if(settings->contains(QString("Port"))){
            ui->portL->setCurrentText(settings->value("Port").toString());
        }
        if(settings->contains(QString("BaudRate"))){
            ui->baudRateL->setCurrentIndex(settings->value("BaudRate").toInt());
        }
        if(settings->contains(QString("DataBits"))){
            ui->dataBitsL->setCurrentIndex(settings->value("DataBits").toInt());
        }
        if(settings->contains(QString("Parity"))){
            ui->parityL->setCurrentIndex(settings->value("Parity").toInt());
        }
        if(settings->contains(QString("StopBits"))){
            ui->stopBitsL->setCurrentIndex(settings->value("StopBits").toInt());
        }
        settings->endGroup();
    }

    if(grouplist.contains(comR)){
        settings->beginGroup(comR);
        if(settings->contains(QString("Port"))){
            ui->portR->setCurrentText(settings->value("Port").toString());
        }
        if(settings->contains(QString("BaudRate"))){
            ui->baudRateR->setCurrentIndex(settings->value("BaudRate").toInt());
        }
        if(settings->contains(QString("DataBits"))){
            ui->dataBitsR->setCurrentIndex(settings->value("DataBits").toInt());
        }
        if(settings->contains(QString("Parity"))){
            ui->parityR->setCurrentIndex(settings->value("Parity").toInt());
        }
        if(settings->contains(QString("StopBits"))){
            ui->stopBitsR->setCurrentIndex(settings->value("StopBits").toInt());
        }
        settings->endGroup();
    }

    delete settings;
    return true;
}

void ComPipeWindow::saveConfig()
{
    const QString filename(QApplication::applicationName() + QString(".ini"));

    QSettings *settings;

    settings = new QSettings(filename, QSettings::IniFormat);
    settings->setValue("ComL/Port", ui->portL->currentText());
    settings->setValue("ComL/BaudRate", ui->baudRateL->currentIndex());
    settings->setValue("ComL/DataBits", ui->dataBitsL->currentIndex());
    settings->setValue("ComL/Parity", ui->parityL->currentIndex());
    settings->setValue("ComL/StopBits", ui->stopBitsL->currentIndex());

    settings->setValue("ComR/Port", ui->portR->currentText());
    settings->setValue("ComR/BaudRate", ui->baudRateR->currentIndex());
    settings->setValue("ComR/DataBits", ui->dataBitsR->currentIndex());
    settings->setValue("ComR/Parity", ui->parityR->currentIndex());
    settings->setValue("ComR/StopBits", ui->stopBitsR->currentIndex());

    settings->sync();

    delete settings;
}

void ComPipeWindow::scanActionTriggered()
{
    QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts();

    QString oldPortL = ui->portL->currentText(), newPortL;
    QString oldPortR = ui->portR->currentText(), newPortR;

    ui->portL->clear();
    ui->portR->clear();
    foreach (QSerialPortInfo port, ports) {
        ui->portL->addItem(port.portName());
        ui->portR->addItem(port.portName());
        if (oldPortL == port.portName()) {
            newPortL = oldPortL;
        }
        if (oldPortR == port.portName()) {
            newPortR = oldPortR;
        }
    }

    if (!newPortL.isEmpty()) {
        ui->portL->setCurrentText(newPortL);
    }
    if (!newPortR.isEmpty()) {
        ui->portR->setCurrentText(newPortR);
    }
}

void ComPipeWindow::startActionTriggered()
{
    QSerialPort::DataBits dataBits;
    QSerialPort::Parity parity;
    QSerialPort::StopBits stopBits;

    if(ui->portL->count() <= 0) return;
    if(ui->baudRateL->count() <= 0) return;
    if(ui->portR->count() <= 0) return;
    if(ui->baudRateR->count() <= 0) return;

    if (m_serialportL == nullptr) {
        QString portL = ui->portL->currentText();
        QString portR = ui->portR->currentText();

        switch (ui->dataBitsL->currentIndex()) {
        case 0:
            dataBits = QSerialPort::Data5;
            break;
        case 1:
            dataBits = QSerialPort::Data6;
            break;
        case 2:
            dataBits = QSerialPort::Data7;
            break;
        case 3:
            dataBits = QSerialPort::Data8;
            break;
        default:
            dataBits = QSerialPort::Data8;
            break;
        }
        switch (ui->parityL->currentIndex()) {
        case 0:
            parity = QSerialPort::NoParity;
            break;
        case 1:
            parity = QSerialPort::EvenParity;
            break;
        case 2:
            parity = QSerialPort::OddParity;
            break;
        case 3:
            parity = QSerialPort::SpaceParity;
            break;
        case 4:
            parity = QSerialPort::MarkParity;
            break;
        default:
            parity = QSerialPort::NoParity;
            break;
        }
        switch (ui->stopBitsL->currentIndex()) {
        case 0:
            stopBits = QSerialPort::OneStop;
            break;
        case 1:
            stopBits = QSerialPort::OneAndHalfStop;
            break;
        case 2:
            stopBits = QSerialPort::TwoStop;
            break;
        default:
            stopBits = QSerialPort::OneStop;
            break;
        }
        m_serialportL = openPort(ui->portL->currentText(),
                                 ui->baudRateL->currentText().toInt(),
                                 dataBits, parity, stopBits);
        if (m_serialportL == nullptr) {
            return;
        }
        connect(m_serialportL, &QSerialPort::readyRead, this, &ComPipeWindow::readyReadL);

        switch (ui->dataBitsR->currentIndex()) {
        case 0:
            dataBits = QSerialPort::Data5;
            break;
        case 1:
            dataBits = QSerialPort::Data6;
            break;
        case 2:
            dataBits = QSerialPort::Data7;
            break;
        case 3:
            dataBits = QSerialPort::Data8;
            break;
        default:
            dataBits = QSerialPort::Data8;
            break;
        }
        switch (ui->parityR->currentIndex()) {
        case 0:
            parity = QSerialPort::NoParity;
            break;
        case 1:
            parity = QSerialPort::EvenParity;
            break;
        case 2:
            parity = QSerialPort::OddParity;
            break;
        case 3:
            parity = QSerialPort::SpaceParity;
            break;
        case 4:
            parity = QSerialPort::MarkParity;
            break;
        default:
            parity = QSerialPort::NoParity;
            break;
        }
        switch (ui->stopBitsR->currentIndex()) {
        case 0:
            stopBits = QSerialPort::OneStop;
            break;
        case 1:
            stopBits = QSerialPort::OneAndHalfStop;
            break;
        case 2:
            stopBits = QSerialPort::TwoStop;
            break;
        default:
            stopBits = QSerialPort::OneStop;
            break;
        }
        if (portL == portR) {
            m_serialportR = m_serialportL;
        } else {
            m_serialportR = openPort(ui->portR->currentText(),
                                     ui->baudRateR->currentText().toInt(),
                                     dataBits, parity, stopBits);
            if (m_serialportR == nullptr) {
                stopActionTriggered();
                return;
            }
            connect(m_serialportR, &QSerialPort::readyRead, this, &ComPipeWindow::readyReadR);
        }

        m_l2r_cnt = 0;
        m_r2l_cnt = 0;
        ui->l2r_counter->setText(QString::number(m_l2r_cnt));
        ui->r2l_counter->setText(QString::number(m_r2l_cnt));

        openEnable(true);
    }
}

void ComPipeWindow::stopActionTriggered()
{
    if ((m_serialportR != m_serialportL) && (m_serialportR != nullptr)) {
        m_serialportR->close();
        delete m_serialportR;
    }
    if (m_serialportL != nullptr) {
        m_serialportL->close();
        delete m_serialportL;
    }

    m_serialportL = nullptr;
    m_serialportR = nullptr;
    openEnable(false);
}

void ComPipeWindow::exitActionTriggered()
{
    qApp->exit(0);
}

void ComPipeWindow::readyReadL()
{
    QByteArray recv;
    qint64 ret;

    recv = m_serialportL->readAll();

    if (recv.isEmpty()) return;

    ret = recv.length();

    m_l2r_cnt += ret;
    ui->l2r_counter->setText(QString::number(m_l2r_cnt));

    m_sfCntL2R += ret;
    m_sfBufL2R += recv;
    m_sfTimL2R.start();

    m_serialportR->write(recv);
}

void ComPipeWindow::readyReadR()
{
    QByteArray recv;
    qint64 ret;

    recv = m_serialportR->readAll();

    if (recv.isEmpty()) return;

    ret = recv.length();

    m_r2l_cnt += ret;
    ui->r2l_counter->setText(QString::number(m_r2l_cnt));

    m_sfCntR2L += ret;
    m_sfBufR2L += recv;
    m_sfTimR2L.start();

    m_serialportL->write(recv);
}

QSerialPort *ComPipeWindow::openPort(const QString &port, qint32 baudRate, QSerialPort::DataBits dataBits, QSerialPort::Parity parity, QSerialPort::StopBits stopBits)
{
    int opened = 0;
    QSerialPort * serialPort = Q_NULLPTR;

    serialPort = new QSerialPort(this);

    if(!serialPort) return Q_NULLPTR;

    serialPort->setPortName(port);
    serialPort->setBaudRate(baudRate);
    serialPort->setDataBits(dataBits);
    serialPort->setParity(parity);
    serialPort->setStopBits(stopBits);

    opened = serialPort->open(QSerialPort::ReadWrite);

    if(!opened){
        ui->info->showInfo(serialPort->portName() + serialPort->errorString());
        delete serialPort;
        serialPort = Q_NULLPTR;
    }

    return serialPort;
}

void ComPipeWindow::openEnable(bool enable)
{
    ui->portL->setEnabled(!enable);
    ui->baudRateL->setEnabled(!enable);
    ui->dataBitsL->setEnabled(!enable);
    ui->parityL->setEnabled(!enable);
    ui->stopBitsL->setEnabled(!enable);
    ui->portR->setEnabled(!enable);
    ui->baudRateR->setEnabled(!enable);
    ui->dataBitsR->setEnabled(!enable);
    ui->parityR->setEnabled(!enable);
    ui->stopBitsR->setEnabled(!enable);

    ui->action_Scan->setEnabled(!enable);
    ui->action_Start->setEnabled(!enable);
    ui->action_Stop->setEnabled(enable);

    checkIfSomeOneCom();
}

void ComPipeWindow::checkIfSomeOneCom()
{
    QString portL = ui->portL->currentText();
    QString portR = ui->portR->currentText();

    if (portL == portR) {
        ui->baudRateR->setEnabled(false);
        ui->dataBitsR->setEnabled(false);
        ui->parityR->setEnabled(false);
        ui->stopBitsR->setEnabled(false);
    }
}

void ComPipeWindow::on_portL_currentTextChanged(const QString &arg1)
{
    openEnable(false);
}

void ComPipeWindow::on_portR_currentTextChanged(const QString &arg1)
{
    openEnable(false);
}

void ComPipeWindow::reloadLuaPlugins()
{
    LuaEngine::instance()->reloadLuaPlugins();
}
